package game.logic.resolver;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.nio.file.Files;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class Resolver
{
    public static void main(String[] args) throws IOException
    {
        Set<String> solutions = new HashSet<>();
        Resolver resolver = new Resolver();
        resolver.searchSolutions(0, 0, solutions);
        resolver.saveSolutions(solutions);
        System.out.printf("找到 %d 组解%n", solutions.size());
    }

    private static final IShape s11 = new Shape11();
    private static final IShape s12 = new Shape12();
    private static final IShape s13 = new Shape13();
    private static final IShape s14 = new Shape14();
    private static final IShape s15 = new Shape15();
    private static final IShape s22 = new Shape22();
    private static final IShape s23 = new Shape23();
    private static final IShape s24 = new Shape24();
    private static final IShape s25 = new Shape25();
    private static final IShape s33 = new Shape33();
    private static final IShape s34 = new Shape34();

    private static final long[][] shapeCodes;

    static
    {
        // 大块优先的搜索策略明显更快，因为可以先排除掉外层的冲突，大幅减少循环次数。
        shapeCodes = new long[11][];
        shapeCodes[0] = s34.getCodes();
        shapeCodes[1] = s33.getCodes();
        shapeCodes[2] = s25.getCodes();
        shapeCodes[3] = s24.getCodes();
        shapeCodes[4] = s23.getCodes();
        shapeCodes[5] = s22.getCodes();
        shapeCodes[6] = s15.getCodes();
        shapeCodes[7] = s14.getCodes();
        shapeCodes[8] = s13.getCodes();
        shapeCodes[9] = s12.getCodes();
        shapeCodes[10] = s11.getCodes();
    }

    private final int[] selection = new int[11];
    private final StringBuilder sbuf = new StringBuilder();

    private void searchSolutions(int shape, long mask, Set<String> solutions)
    {
        // 递归查询，等价于嵌套循环，但写起来比嵌套循环简洁好看。

        // 所有形状都摆完，找到一组解
        if (shape >= shapeCodes.length)
        {
            testSolution(solutions);
            return;
        }

        // 检查同一形状在盘面里所有可摆放位置的组合，看是否与前序摆放冲突。
        // 冲突则继续尝试下一个位置，不冲突则尝试下一个形状（继续深入递归）。
        long[] codes = shapeCodes[shape];
        for (int i = 0; i < codes.length; i++)
        {
            long code = codes[i];
            long newMask = mask ^ code;
            if ((mask | code) == newMask)
            {
                // 记住当前形状，搜索下一个形状
                selection[shape] = i;
                searchSolutions(shape + 1, newMask, solutions);
            }
        }
    }

    private void testSolution(Set<String> solutions)
    {
        // 给不同形状坐上标记，用于比较
        char[] solution = new char[64];
        fill('X', shapeCodes[0][selection[0]], solution);
        fill('0', shapeCodes[1][selection[1]], solution);
        fill('9', shapeCodes[2][selection[2]], solution);
        fill('8', shapeCodes[3][selection[3]], solution);
        fill('7', shapeCodes[4][selection[4]], solution);
        fill('6', shapeCodes[5][selection[5]], solution);
        fill('5', shapeCodes[6][selection[6]], solution);
        fill('4', shapeCodes[7][selection[7]], solution);
        fill('3', shapeCodes[8][selection[8]], solution);
        fill('2', shapeCodes[9][selection[9]], solution);
        fill('1', shapeCodes[10][selection[10]], solution);

        // 根据方阵与方块的几何特性，任意一种解法，都存在以下与之对称的形式：
        // 旋转对称：旋转90°、180°、270°
        // 镜像对称：水平翻转、垂直翻转、对角线翻转（两个对角线）
        // 所以当某种解法的某一对称形态已经出现时，该解法视为重复解法。
        // 但仍然将该解保留。
        String origin = draw(sbuf, solution);
        solutions.add(origin);
    }

    private void saveSolutions(Collection<String> solutions) throws IOException
    {
        File file1 = new File("solutions.txt");
        File file2 = new File("solutions_kv.txt");
        try (Writer writer1 = Files.newBufferedWriter(file1.toPath());
             Writer writer2 = Files.newBufferedWriter(file2.toPath()))
        {
            long i = 1;
            for (String shape : solutions)
            {
                writer1.append("[组合").append(Long.toString(i)).append("]")
                       .append(System.lineSeparator())
                       .append(shape)
                       .append(System.lineSeparator());

                String solution = shape.replace(System.lineSeparator(), "");
                String identity = solution.replaceAll("[04-9X]", "_");
                writer2.append(identity)
                       .append(",")
                       .append(solution)
                       .append(System.lineSeparator());
                i++;
            }
        }
    }

    private static void fill(char c, long code, char[] solution)
    {
        for (int i = 0; i < 64; i++)
        {
            long bit = (code >>> (63 - i)) & 1;
            if (bit > 0)
                solution[i] = c;
        }
    }

    private static String draw(StringBuilder sbuf, char[] solution)
    {
        sbuf.setLength(0);
        for (int r = 0; r < 8; r++)
        {
            for (int c = 0; c < 8; c++)
                sbuf.append(solution[r * 8 + c]);
            sbuf.append(System.lineSeparator());
        }
        return sbuf.toString();
    }
}
